/*
 * GammaEliasFede.h
 *
 *  Created on: Nov 29, 2012
 *      Author: Federico Orquera
 */

#ifndef GAMMAELIASFEDE_H_
#define GAMMAELIASFEDE_H_

#include <vector>
#include <iostream>
#include <stdio.h>
#include <math.h>

#include "../Compressor.h"

namespace physical {

#define GET_BIT_INDEX(I,J,SIZE) (I + (J - sizeof(counter_t))*8)
#define GET_REVERSE_BIT_INDEX(I,J,SIZE) (SIZE-sizeof(counter_t))*8 - (I + (J - sizeof(counter_t))*8) - 1

#define GET_DATA_INDEX(I,J,SIZE) J
#define GET_REVERSE_DATA_INDEX(I,J,SIZE) SIZE-J+sizeof(counter_t)-1

/**
 * Tabla de compresion:
 *
 * 0 = 2^0 + 0	10			1/2
 * 1 = 2^0 + 1	11			1/2
 * 2 = 2^1 + 0	010			1/8
 * 3 = 2^1 + 1	011			1/8
 * 4 = 2^2 + 0	00100		1/32
 * 5 = 2^2 + 1	00101		1/32
 * 6 = 2^2 + 2	00110		1/32
 * 7 = 2^2 + 3	00111		1/32
 * 8 = 2^3 + 0	0001000		1/128
 * 9 = 2^3 + 1	0001001		1/128
 * 10 = 2^3 + 2	0001010		1/128
 * 11 = 2^3 + 3	0001011		1/128
 * 12 = 2^3 + 4	0001100		1/128
 * 13 = 2^3 + 5	0001101		1/128
 * 14 = 2^3 + 6	0001110		1/128
 * 15 = 2^3 + 7	0001111		1/128
 * 16 = 2^4 + 0	000010000	1/512
 * 17 = 2^4 + 1	000010001	1/512
 * (...)
 */

class GammaEliasCoder: public Compressor {

public:

    typedef unsigned short coding_type;
    typedef unsigned char counter_t;

private:

    counter_t encoded_bytes;

	void printVector(std::vector<bool>  vec) {

		int j = 0;

	     for ( unsigned int i = 0; i < vec.size(); i++ ){
	          std::cout << vec[i];// << " ";
	          j++;
	          if( j == 8 ){
	        	  std::cout << " ";
	        	  j = 0;
	          }
	     }

	     std::cout << std::endl;
	}

	void printList(std::list<bool>  lis) {

		int j = 0;

		 for (auto it = lis.begin(); it != lis.end(); it++ ){
			  std::cout << ((*it)?"1":"0");// << " ";
			  j++;
			  if( j == 8 ){
				  std::cout << " ";
				  j = 0;
			  }
		 }

		 std::cout << std::endl;
	}

	std::vector<bool> encode( const byte* source, size_t size ){

		std::vector<bool> bitwriter;

		this->encoded_bytes = 0;

		for ( size_t i = 0; i < size; i = i + sizeof(coding_type) ){ //  Itero sobre todo el vector de datos

			coding_type num =((coding_type*)source)[this->encoded_bytes];

			//std::cout<<"i: "<<i<<std::endl;

			//std::cout<<"num: "<<num<<std::endl;

			/*Es 0?*/
			if(num == 0){ 					// <--- Logaritmo de 0 es infinito!
				bitwriter.push_back(true);
				bitwriter.push_back(false);
			}

			/*Es 1?*/
			else if(num == 1){ 				// <--- Logaritmo de 1 es 0!
				bitwriter.push_back(true);
				bitwriter.push_back(true);
			}

			else {

				unsigned int l = floor( log2(num) );

				//std::cout<<"log2(num): "<<log2(num)<<std::endl;
				//std::cout<<"l = floor( log2(num) ): "<<l<<std::endl;

				for (unsigned int a=0; a < l; a++)
					bitwriter.push_back(false); 	// Pongo tantos 0, como el logaritmo en base 2 del numero

				bitwriter.push_back(true);      	// Un 1 al final de los 0

				for (int a=l-1; a >= 0; a--) 		// Escribo el "resto" en binario puro
					if (num & 1 << a)
						bitwriter.push_back(true);
					else
						bitwriter.push_back(false);

			}

			//std::cout<<"El vector comprimido fue: "<<std::endl;this->printVector(bitwriter);
			this->encoded_bytes++;

		}

		return bitwriter;
	}

	size_t vectToChar( std::vector<bool> bitVector, byte* dest ){

		//std::cout<<"El vector comprimido fue: "<<std::endl;this->printVector(bitVector);

		size_t size = (
			ceil((float)bitVector.size()/8) + // Cuantos bytes necesito?
			sizeof(counter_t) 				  // Cuantos bytes encodie?
		);

		((counter_t*)dest)[0] = this->encoded_bytes;

		size_t j = 0;

		for(size_t i = sizeof(counter_t); i<size; i++ ){
			// TODO seguro hay una forma mejor de hacer estos for, pero estoy medio quemado
			dest[i] = 0;

			size_t k;

			for(k=0; k<8 && (j+k)<bitVector.size(); k++)
				dest[i] = (dest[i] * 2) | ((bitVector[j+k]==true)?1:0);

			if(k<8) dest[i] = dest[i] << (8-k);

			j = j+8;
		}

		return size;
	}

	std::vector<bool> charToVect( const byte* compressedData, size_t compressedDataSize ){

		std::vector<bool> returnVector;

		returnVector.resize( (compressedDataSize-sizeof(counter_t))*8 );

		this->encoded_bytes = *((counter_t*)compressedData);

		//std::cout<<"encoded_bytes: "<<this->encoded_bytes<<std::endl;

		for(size_t j = sizeof(counter_t); j<compressedDataSize; j++)
			for(unsigned int i = 0; i < 8; i++){
				//std::cout<<"GET_REVERSE_BIT_INDEX(I,J,SIZE): " <<GET_REVERSE_BIT_INDEX(i,j,compressedDataSize)<<std::endl;
				//std::cout<<"GET_REVERSE_DATA_INDEX(I,J,SIZE): "<<GET_REVERSE_DATA_INDEX(i,j,compressedDataSize)<<std::endl;
				returnVector[GET_REVERSE_BIT_INDEX(i,j,compressedDataSize)] = (
						((
						1 << i)  				// roto la mascara
						& compressedData[GET_REVERSE_DATA_INDEX(i,j,compressedDataSize)]) 	// aplico la mascara
						!= 0					// es 0?
				);
				//this->printVector(returnVector);
			}

		return returnVector;
	}

	std::list<bool> charToList( const byte* compressedData, size_t compressedDataSize ){

		std::list<bool> returnVector;

		this->encoded_bytes = *((counter_t*)compressedData);

		//std::cout<<"encoded_bytes: "<<this->encoded_bytes<<std::endl;

		for(size_t j = sizeof(counter_t); j<compressedDataSize; j++)
			for(unsigned int i = 0; i < 8; i++){
				returnVector.push_front(
						((
						1 << i)  				// roto la mascara
						& compressedData[GET_REVERSE_DATA_INDEX(i,j,compressedDataSize)]) 	// aplico la mascara
						!= 0					// es 0?
				);
				//this->printList(returnVector);
			}

		return returnVector;
	}

	size_t decode( std::list<bool> source, byte* data ){

		//std::cout<<std::endl<<"Iniciando decodificacion"<<std::endl;
		//this->printList(source);

	    counter_t decodedBytes = 0;

		size_t returnSize = 0;

		while (!source.empty() && decodedBytes < this->encoded_bytes)
		{
			int numberBits = 0;
			coding_type current = 0;

			if(source.front()){
				source.pop_front();
				if(source.front()) current = 1;
				else current = 0;
				source.pop_front();
			}
			else {

				//std::cout<<"No es caso especial!"<<std::endl;

				while (!source.front() && !source.empty()){
					numberBits++; //keep on reading until we fetch a one...
					source.pop_front();
				}

				//std::cout<<"numberBits: "<<numberBits<<std::endl;

				source.pop_front();

				for (int a=numberBits-1; a >= 0; a--) //Read numberBits bits
				{
					if (source.front()){
						current |= 1 << a;
						source.pop_front();
					}
					else source.pop_front();
				}
				current |= 1 << numberBits; //leading 1 isn't encoded!
			}
			((coding_type*)data)[decodedBytes]=current;
			decodedBytes++;

			//if(source.size() != 1)std::cout<<"decodifico un: "<<current<<std::endl;
			//this->printList(source);
			returnSize++;
		}

		return returnSize*sizeof(coding_type);
	}

public:

	GammaEliasCoder(){
		this->encoded_bytes = 0;
	};

	virtual ~GammaEliasCoder(){}

	 /**
	 * Comprime un array de bytes
	 *
	 * @param data Array de bytes a comprimir
	 * @param compressedData Buffer de bytes donde se guarda la información comprimida
	 * @param dataSize Tamaño del array de bytes a comprimir
	 * @return Tamaño del array de bytes con la información comprimida
	 */
	virtual size_t compress( const byte* data, byte* compressedData,
            size_t dataSize ){
		return vectToChar( this->encode(data, dataSize), compressedData );
	};

	/**
	 * Descomprime un array de bytes
	 *
	 * @param compressedData Array bytes con la información comprimida
	 * @param data Array de bytes donde se guarda la información descomprimida
	 * @param compressedDataSize Tamaño del array de bytes a descomprimir
	 * @return Tamaño del array de bytes con la información descomprimida
	 */

	virtual size_t uncompress( const byte* compressedData, byte* data,
            size_t compressedDataSize ){

		return decode(this->charToList(compressedData,compressedDataSize), data);
	}
};

} /* namespace GammaEliasCoder */


#endif /* GAMMAELIASFEDE_H_ */
